package handler

import (
	"context"
	"encoding/json"
	"gitee.com/fangy1111/shop/inventory"
	"github.com/apache/rocketmq-client-go/v2/consumer"
	"github.com/apache/rocketmq-client-go/v2/primitive"
	"github.com/go-redsync/redsync/v4"
	"github.com/go-redsync/redsync/v4/redis/goredis/v9"
	goredislib "github.com/redis/go-redis/v9"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"inventory_srv/global"

	"inventory_srv/model/mysql"
	"time"
)

type InventoryService struct {
	inventory.UnimplementedInventoryServer
}

// 设置库存
func (i *InventoryService) SetInv(ctx context.Context, in *inventory.GoodsInvInfo) (*inventory.InventoryEmpty, error) {
	var inv mysql.Inventory
	global.DB.Where(&mysql.Inventory{GoodsId: in.GoodsId}).First(&inv)
	inv.GoodsId = in.GoodsId
	inv.Stock = in.Stock
	res := global.DB.Save(&inv)
	if res.Error != nil {
		return nil, status.Error(codes.Internal, "设置库存失败")
	}
	return &inventory.InventoryEmpty{}, nil
}

// 获取商品库存信息
func (i *InventoryService) GetStockDetail(ctx context.Context, in *inventory.GoodsInvInfo) (*inventory.GoodsInvInfo, error) {
	var inv mysql.Inventory
	res := global.DB.Where(&mysql.Inventory{GoodsId: in.GoodsId}).First(&inv)
	if res.RowsAffected == 0 {
		return nil, status.Error(codes.Internal, "没有该商品的库存信息")
	}
	return &inventory.GoodsInvInfo{
		GoodsId: inv.GoodsId,
		Stock:   inv.Stock,
	}, nil
}

// 悲观锁扣减库存 防止超卖
//func (i *InventoryService) Sell(ctx context.Context, in *inventory.SellInfo) (*inventory.Empty, error) {
//	//扣减库存 本地事务
//	//并发情况下可能会出现超卖
//	tx := global.DB.Begin()
//
//	for _, v := range in.GoodsInfo {
//
//		var inv mysql.Inventory
//
//		res := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where(&mysql.Inventory{GoodsId: v.GoodsId}).First(&inv)
//		if res.RowsAffected == 0 {
//			tx.Rollback()
//			return nil, status.Error(codes.Internal, "没有该商品的库存信息")
//		}
//
//		if inv.Stock < v.Stock {
//			tx.Rollback()
//			return nil, status.Error(codes.Internal, "库存不足")
//		}
//
//		inv.Stock -= v.Stock
//
//		tx := tx.Save(&inv)
//		if tx.Error != nil {
//			tx.Rollback()
//			return nil, status.Error(codes.Internal, "扣减库存失败")
//		}
//	}
//	tx.Commit()
//	return &inventory.Empty{}, nil
//}

// 乐观锁扣减库存 防止超卖
//func (i *InventoryService) Sell(ctx context.Context, in *inventory.SellInfo) (*inventory.Empty, error) {
//	//扣减库存 本地事务
//	//并发情况下可能会出现超卖
//	var inv mysql.Inventory
//
//	for _, v := range in.GoodsInfo {
//		//如果没有成功一直重试
//		for true {
//			res := global.DB.Where(&mysql.Inventory{GoodsId: v.GoodsId}).First(&inv)
//			if res.RowsAffected == 0 {
//				return nil, status.Error(codes.Internal, "没有该商品的库存信息")
//			}
//
//			if inv.Stock < v.Stock {
//				return nil, status.Error(codes.Internal, "库存不足")
//			}
//
//			//扣减，会出现数据不一致的问题
//			tx := global.DB.Select("stock", "version").Where("goods_id = ?", v.GoodsId).Where("version = ?", inv.Version).Updates(mysql.Inventory{Stock: inv.Stock - 1, Version: inv.Version + 1})
//			if tx.RowsAffected == 1 {
//				break
//			}
//		}
//	}
//
//	return &inventory.Empty{}, nil
//}

// redis 分布式锁
func (i *InventoryService) Sell(ctx context.Context, in *inventory.SellInfo) (*inventory.InventoryEmpty, error) {
	//连接redis
	client := goredislib.NewClient(&goredislib.Options{
		Addr: "127.0.0.1:6379",
	})
	pool := goredis.NewPool(client)
	rs := redsync.New(pool)

	mutexName := "inventory-mutex"
	//设置锁过期时间为10秒，重试5次
	mutex := rs.NewMutex(mutexName, redsync.WithExpiry(10*time.Second), redsync.WithTries(5))

	//尝试加锁
	if err := mutex.LockContext(ctx); err != nil {
		return nil, status.Errorf(codes.Internal, "获取redis锁失败")
	}

	//启动一个新的数据库事务
	tx := global.DB.Begin()

	var orderInventorySell []*mysql.OrderInventorySell

	for _, v := range in.GoodsInfo {

		var inv mysql.Inventory
		res := tx.Where(&mysql.Inventory{GoodsId: v.GoodsId}).First(&inv)

		if res.RowsAffected == 0 {
			tx.Rollback()
			return nil, status.Errorf(codes.Internal, "没有该商品库存信息")
		}

		if inv.Stock < v.Stock {
			tx.Rollback()
			return nil, status.Errorf(codes.Internal, "库存不足")
		}

		inv.Stock -= v.Stock
		if err := tx.Save(&inv).Error; err != nil {
			tx.Rollback()
			return nil, status.Errorf(codes.Internal, "更新库存失败: %v", err)
		}

		//组装订单扣减库存表
		orderInventorySell = append(orderInventorySell, &mysql.OrderInventorySell{
			OrderSn: in.OrderSn,
			GoodsID: v.GoodsId,
			Stocks:  v.Stock,
			Status:  1,
		})

	}

	batches := tx.Model(&mysql.OrderInventorySell{}).CreateInBatches(&orderInventorySell, 100)

	if batches.Error != nil {
		tx.Rollback()
		return nil, status.Errorf(codes.Internal, "批量添加订单库存扣减记录失败: %v", batches.Error)
	}

	//提交事务
	tx.Commit()
	// 数据库操作完成后解锁
	if ok, err := mutex.UnlockContext(ctx); !ok || err != nil {
		return nil, status.Errorf(codes.Internal, "释放Redis锁失败: %v", err)
	}
	return &inventory.InventoryEmpty{}, nil

}

// 尝试扣减库存
func (i *InventoryService) TrySell(ctx context.Context, in *inventory.SellInfo) (*inventory.InventoryEmpty, error) {
	//连接redis
	client := goredislib.NewClient(&goredislib.Options{
		Addr: "127.0.0.1:6379",
	})
	pool := goredis.NewPool(client)
	rs := redsync.New(pool)

	mutexName := "inventory-mutex"
	//设置锁过期时间为10秒，重试5次
	mutex := rs.NewMutex(mutexName, redsync.WithExpiry(10*time.Second), redsync.WithTries(5))

	//尝试加锁
	if err := mutex.LockContext(ctx); err != nil {
		return nil, status.Errorf(codes.Internal, "获取redis锁失败")
	}

	//启动一个新的数据库事务
	tx := global.DB.Begin()

	for _, v := range in.GoodsInfo {

		var inv mysql.Inventory

		res := tx.Where(&mysql.Inventory{GoodsId: v.GoodsId}).First(&inv)

		if res.RowsAffected == 0 {
			tx.Rollback()
			return nil, status.Errorf(codes.Internal, "没有该商品库存信息")
		}

		if inv.Stock < v.Stock {
			tx.Rollback()
			return nil, status.Errorf(codes.Internal, "库存不足")
		}

		inv.Freeze += v.Stock
		if err := tx.Save(&inv).Error; err != nil {
			tx.Rollback()
			return nil, status.Errorf(codes.Internal, "更新库存失败: %v", err)
		}
	}
	//提交事务
	tx.Commit()
	// 数据库操作完成后解锁
	if ok, err := mutex.UnlockContext(ctx); !ok || err != nil {
		return nil, status.Errorf(codes.Internal, "释放Redis锁失败: %v", err)
	}
	return &inventory.InventoryEmpty{}, nil

}

// 确认扣减库存
func (i *InventoryService) ConfirmSell(ctx context.Context, in *inventory.SellInfo) (*inventory.InventoryEmpty, error) {
	//连接redis
	client := goredislib.NewClient(&goredislib.Options{
		Addr: "127.0.0.1:6379",
	})
	pool := goredis.NewPool(client)
	rs := redsync.New(pool)

	mutexName := "inventory-mutex"
	//设置锁过期时间为10秒，重试5次
	mutex := rs.NewMutex(mutexName, redsync.WithExpiry(10*time.Second), redsync.WithTries(5))

	//尝试加锁
	if err := mutex.LockContext(ctx); err != nil {
		return nil, status.Errorf(codes.Internal, "获取redis锁失败")
	}

	//启动一个新的数据库事务
	tx := global.DB.Begin()

	for _, v := range in.GoodsInfo {

		var inv mysql.Inventory
		res := tx.Where(&mysql.Inventory{GoodsId: v.GoodsId}).First(&inv)

		if res.RowsAffected == 0 {
			tx.Rollback()
			return nil, status.Errorf(codes.Internal, "没有该商品库存信息")
		}

		if inv.Stock < v.Stock {
			tx.Rollback()
			return nil, status.Errorf(codes.Internal, "库存不足")
		}

		inv.Stock -= v.Stock
		inv.Freeze -= v.Stock
		if err := tx.Save(&inv).Error; err != nil {
			tx.Rollback()
			return nil, status.Errorf(codes.Internal, "更新库存失败: %v", err)
		}
	}
	//提交事务
	tx.Commit()
	// 数据库操作完成后解锁
	if ok, err := mutex.UnlockContext(ctx); !ok || err != nil {
		return nil, status.Errorf(codes.Internal, "释放Redis锁失败: %v", err)
	}
	return &inventory.InventoryEmpty{}, nil

}

// 取消扣减库存
func (i *InventoryService) CancelSell(ctx context.Context, in *inventory.SellInfo) (*inventory.InventoryEmpty, error) {
	client := goredislib.NewClient(&goredislib.Options{
		Addr: "127.0.0.1:6379",
	})
	pool := goredis.NewPool(client)

	rs := redsync.New(pool)

	mutexname := "inventory-mutex"
	mutex := rs.NewMutex(mutexname, redsync.WithExpiry(10*time.Second), redsync.WithTries(5))

	//尝试加锁
	if err := mutex.LockContext(ctx); err != nil {
		return nil, status.Errorf(codes.Internal, "获取redis锁失败")
	}

	//开启事务
	tx := global.DB.Begin()

	for _, v := range in.GoodsInfo {
		var inv mysql.Inventory
		res := tx.Where(mysql.Inventory{GoodsId: v.GoodsId}).First(&inv)

		if res.RowsAffected == 0 {
			tx.Rollback()
			return nil, status.Errorf(codes.Internal, "没有该商品库存信息")
		}
		if inv.Stock < v.Stock {
			tx.Rollback()
			return nil, status.Errorf(codes.Internal, "库存不足")
		}
		inv.Freeze -= v.Stock
		if err := tx.Save(&inv).Error; err != nil {
			tx.Rollback()
			return nil, status.Errorf(codes.Internal, "更新库存失败")
		}
	}
	//提交事务
	tx.Commit()
	if ok, err := mutex.UnlockContext(ctx); !ok || err != nil {
		return nil, status.Errorf(codes.Internal, "释放redis锁失败")
	}
	return &inventory.InventoryEmpty{}, nil
}

func (i *InventoryService) Rollback(ctx context.Context, in *inventory.SellInfo) (*inventory.InventoryEmpty, error) {
	//1.根据订单号去订单售卖记录表找到要归还订单的库存信息
	var orderInventorySell []*mysql.OrderInventorySell

	res := global.DB.Model(&mysql.OrderInventorySell{}).Where(mysql.OrderInventorySell{
		OrderSn: in.OrderSn,
		Status:  1,
	}).Find(&orderInventorySell)
	if res.Error != nil {
		return nil, status.Errorf(codes.Internal, "没有该订单扣减库存记录")
	}

	//2.进行归还库存
	tx := global.DB.Begin()
	for _, v := range orderInventorySell {
		var inv mysql.Inventory
		resu := tx.Model(&mysql.Inventory{}).Where(mysql.Inventory{GoodsId: v.GoodsID}).First(&inv)
		if resu.Error != nil {
			tx.Rollback()
			return nil, status.Errorf(codes.Internal, "没有该商品库存信息")
		}
		inv.Stock += v.Stocks
		resu = tx.Save(&inv)
		if resu.Error != nil {
			tx.Rollback()
			return nil, status.Errorf(codes.Internal, "归还库存失败")
		}
	}
	//更新订单扣减库存记录的状态
	res = tx.Model(&mysql.OrderInventorySell{}).Where(mysql.OrderInventorySell{OrderSn: in.OrderSn}).Update("status", 2)
	if res.Error != nil {
		tx.Rollback()
		return nil, status.Errorf(codes.Internal, "更新订单扣减记录状态失败")
	}
	tx.Commit()
	return &inventory.InventoryEmpty{}, nil
}

type OrderInfo struct {
	OrderSn string
}

// 自动归还库存
func AutoReback(ctx context.Context, msgs ...*primitive.MessageExt) (consumer.ConsumeResult, error) {

	for i := range msgs {

		var orderInfo OrderInfo
		json.Unmarshal(msgs[i].Body, &orderInfo)

		//根据订单号去订单库存扣减记录表获取要归还的订单信息
		//进行库存归还
		var orderInventorySell []mysql.OrderInventorySell

		res := global.DB.Model(&mysql.OrderInventorySell{}).Where(mysql.OrderInventorySell{OrderSn: orderInfo.OrderSn, Status: 1}).Find(&orderInventorySell)

		if res.Error != nil {
			zap.S().Panic("没有该订单的库存扣减记录")
			return consumer.ConsumeRetryLater, nil
		}

		tx := global.DB.Begin()
		for _, v := range orderInventorySell {
			var inv mysql.Inventory
			first := tx.Model(&mysql.Inventory{}).Where(mysql.Inventory{GoodsId: v.GoodsID}).First(&inv)
			if first.RowsAffected == 0 {
				tx.Rollback()
				zap.S().Info("没有该商品的库存信息")
				return consumer.ConsumeRetryLater, nil
			}
			inv.Stock += v.Stocks
			save := tx.Save(&inv)
			if save.Error != nil {
				tx.Rollback()
				zap.S().Info("更新库存信息失败")
				return consumer.ConsumeRetryLater, nil
			}
		}

		//更新订单库存扣减表状态
		update := tx.Model(&mysql.OrderInventorySell{}).Where(mysql.OrderInventorySell{OrderSn: orderInfo.OrderSn}).Update("status", 2)
		if update.Error != nil {
			tx.Rollback()
			zap.S().Info("更新订单库存扣减表状态失败")
			return consumer.ConsumeRetryLater, nil
		}
		tx.Commit()
	}

	return consumer.ConsumeSuccess, nil
}
